package com.daffodil.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;

import lombok.extern.slf4j.Slf4j;

/**
 * 通用http发送方法
 * 
 * @author yweijian
 * @date 2019年8月18日
 * @version 1.0
 */
@Slf4j
public class HttpUtils {
	
	/**
	 * 向指定 URL 发送GET方法的请求
	 * <br>默认请求头：
	 * <br> accept = *\/*
	 * <br> connection = Keep-Alive
	 * <br> Cache-Control = no-cache
	 * <br> user-agent = Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)
	 * <br> Accept-Charset = UTF-8
	 * <br> Content-Type = application/json;charset=UTF-8
	 * @param url 发送请求的 URL
	 * @return 所代表远程资源的响应结果
	 */
	public static String sendGet(String url) {
		return sendGet(url, null, null);
	}
	
	/**
	 * 向指定 URL 发送GET方法的请求
	 * <br>默认请求头：
	 * <br> accept = *\/*
	 * <br> connection = Keep-Alive
	 * <br> Cache-Control = no-cache
	 * <br> user-agent = Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)
	 * <br> Accept-Charset = UTF-8
	 * <br> Content-Type = application/json;charset=UTF-8
	 * @param url 发送请求的 URL
	 * @param param 请求参数，请求参数应该是 name1=value1&name2=value2 的形式
	 * @return 所代表远程资源的响应结果
	 */
	public static String sendGet(String url, String param) {
		return sendGet(url, param, null);
	}
	
	/**
	 * 向指定 URL 发送GET方法的请求
	 * <br>默认请求头：
	 * <br> accept = *\/*
	 * <br> connection = Keep-Alive
	 * <br> Cache-Control = no-cache
	 * <br> user-agent = Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)
	 * <br> Accept-Charset = UTF-8
	 * <br> Content-Type = application/json;charset=UTF-8
	 * @param url 发送请求的 URL
	 * @param param 请求参数，请求参数应该是 name1=value1&name2=value2 的形式
	 * @param header 请求头Map<String, String>
	 * @return 所代表远程资源的响应结果
	 */
	public static String sendGet(String url, String param, Map<String, String> header) {
		String result = "";
		InputStream in = null;
		try {
			url = url + (StringUtils.isNotEmpty(param) ? "?" + param : "");
			URL realUrl = new URL(url);
			URLConnection connection = realUrl.openConnection();
			Map<String, String> defaultHeader = createRequestHeader(header);
			defaultHeader.forEach((key, value) -> {
				connection.setRequestProperty(key, value);
			});
			connection.connect();
			String charset = defaultHeader.get("Accept-Charset");
			charset = StringUtils.isNotEmpty(charset) ? charset : StandardCharsets.UTF_8.toString();
			in = connection.getInputStream();
			result = IOUtils.toString(in, charset);
			log.info("调用HttpUtils.sendGet Response, url={}, param={}, result={}", url, param, result.length() > 1024 ? result.substring(0, 1024) : result);
		} catch (ConnectException e) {
			log.error("调用HttpUtils.sendGet ConnectException, url={}, param={}", url, param, e);
		} catch (SocketTimeoutException e) {
			log.error("调用HttpUtils.sendGet SocketTimeoutException, url={}, param={}", url, param, e);
		} catch (IOException e) {
			log.error("调用HttpUtils.sendGet IOException, url={}, param={}", url, param, e);
		} catch (Exception e) {
			log.error("调用HttpsUtil.sendGet Exception, url={}, param={}", url, param, e);
		} finally {
			try {
				if (in != null) {
					in.close();
				}
			} catch (Exception ex) {
				log.error(ex.getMessage(),ex);
			}
		}
		return result;
	}

	/**
	 * 向指定 URL 发送POST方法的请求
	 * <br>默认请求头：
	 * <br> accept = *\/*
	 * <br> connection = Keep-Alive
	 * <br> Cache-Control = no-cache
	 * <br> user-agent = Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)
	 * <br> Accept-Charset = UTF-8
	 * <br> Content-Type = application/json;charset=UTF-8
	 * @param url 发送请求的 URL
	 * @return 所代表远程资源的响应结果
	 */
	public static String sendPost(String url) {
		return sendPost(url, null, null, null);
	}
	
	/**
	 * 向指定 URL 发送POST方法的请求
	 * <br>默认请求头：
	 * <br> accept = *\/*
	 * <br> connection = Keep-Alive
	 * <br> Cache-Control = no-cache
	 * <br> user-agent = Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)
	 * <br> Accept-Charset = UTF-8
	 * <br> Content-Type = application/json;charset=UTF-8
	 * @param url 发送请求的 URL
	 * @param param 请求参数，请求参数应该是 name1=value1&name2=value2 的形式
	 * @return 所代表远程资源的响应结果
	 */
	public static String sendPost(String url, String param) {
		return sendPost(url, param, null, null);
	}
	
	/**
	 * 向指定 URL 发送POST方法的请求
	 * <br>默认请求头：
	 * <br> accept = *\/*
	 * <br> connection = Keep-Alive
	 * <br> Cache-Control = no-cache
	 * <br> user-agent = Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)
	 * <br> Accept-Charset = UTF-8
	 * <br> Content-Type = application/json;charset=UTF-8
	 * @param url 发送请求的 URL
	 * @param param 请求参数，请求参数应该是 name1=value1&name2=value2 的形式
	 * @param body 请求体参数text/json
	 * @return 所代表远程资源的响应结果
	 */
	public static String sendPost(String url, String param, String body) {
		return sendPost(url, param, body, null);
	}
	
	/**
	 * 向指定 URL 发送POST方法的请求
	 * <br>默认请求头：
	 * <br> accept = *\/*
	 * <br> connection = Keep-Alive
	 * <br> Cache-Control = no-cache
	 * <br> user-agent = Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)
	 * <br> Accept-Charset = UTF-8
	 * <br> Content-Type = application/json;charset=UTF-8
	 * @param url 发送请求的 URL
	 * @param param 请求参数，请求参数应该是 name1=value1&name2=value2 的形式
	 * @param body 请求体参数text/json
	 * @param header 请求头Map<String, String>
	 * @return 所代表远程资源的响应结果
	 */
	public static String sendPost(String url, String param, String body, Map<String, String> header) {
		String result = "";
		InputStream in = null;
		OutputStream out = null;
		try {
			url = url + (StringUtils.isNotEmpty(param) ? "?" + param : "");
			URL realUrl = new URL(url);
			URLConnection connection = realUrl.openConnection();
			connection.setDoOutput(true);
			connection.setDoInput(true);
			Map<String, String> defaultHeader = createRequestHeader(header);
			defaultHeader.forEach((key, value) -> {
				connection.setRequestProperty(key, value);
			});
			
			String charset = defaultHeader.get("Accept-Charset");
			charset = StringUtils.isNotEmpty(charset) ? charset : StandardCharsets.UTF_8.toString();
			
			if(StringUtils.isNotEmpty(body)) {
				out = connection.getOutputStream();
				IOUtils.write(body, out, charset);
			}
			
			in = connection.getInputStream();
			result = IOUtils.toString(in, charset);
			log.info("调用HttpUtils.sendPost Response, url={}, param={}, body={}, result={}", url, param, result.length() > 1024 ? result.substring(0, 1024) : result);
		} catch (ConnectException e) {
			log.error("调用HttpUtils.sendPost ConnectException, url={}, param={}, body={}", url, param, body, e);
		} catch (SocketTimeoutException e) {
			log.error("调用HttpUtils.sendPost SocketTimeoutException, url={}, param={}, body={}", url, param, body, e);
		} catch (IOException e) {
			log.error("调用HttpUtils.sendPost IOException, url={}, param={}, body={}", url, param, body, e);
		} catch (Exception e) {
			log.error("调用HttpsUtil.sendPost Exception, url={}, param={}, body={}", url, param, body, e);
		} finally {
			try {
				if (out != null) {
					out.close();
				}
				if (in != null) {
					in.close();
				}
			} catch (IOException ex) {
				log.error(ex.getMessage(),ex);
			}
		}
		return result;
	}

	/**
	 * 向指定 URL 发送POST方法的请求
	 * <br>默认请求头：
	 * <br> accept = *\/*
	 * <br> connection = Keep-Alive
	 * <br> Cache-Control = no-cache
	 * <br> user-agent = Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)
	 * <br> Accept-Charset = UTF-8
	 * <br> Content-Type = application/json;charset=UTF-8
	 * @param url 发送请求的 URL
	 * @return 所代表远程资源的响应结果
	 */
	public static String sendSSLPost(String url) {
		return sendSSLPost(url, null, null, null);
	}
	
	/**
	 * 向指定 URL 发送POST方法的请求
	 * <br>默认请求头：
	 * <br> accept = *\/*
	 * <br> connection = Keep-Alive
	 * <br> Cache-Control = no-cache
	 * <br> user-agent = Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)
	 * <br> Accept-Charset = UTF-8
	 * <br> Content-Type = application/json;charset=UTF-8
	 * @param url 发送请求的 URL
	 * @param param 请求参数，请求参数应该是 name1=value1&name2=value2 的形式。
	 * @return 所代表远程资源的响应结果
	 */
	public static String sendSSLPost(String url, String param) {
		return sendSSLPost(url, param, null, null);
	}
	
	/**
	 * 向指定 URL 发送POST方法的请求
	 * <br>默认请求头：
	 * <br> accept = *\/*
	 * <br> connection = Keep-Alive
	 * <br> Cache-Control = no-cache
	 * <br> user-agent = Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)
	 * <br> Accept-Charset = UTF-8
	 * <br> Content-Type = application/json;charset=UTF-8
	 * @param url 发送请求的 URL
	 * @param param 请求参数，请求参数应该是 name1=value1&name2=value2 的形式。
	 * @param body 请求体参数text/json
	 * @return 所代表远程资源的响应结果
	 */
	public static String sendSSLPost(String url, String param, String body) {
		return sendSSLPost(url, param, body, null);
	}
	
	/**
	 * 向指定 URL 发送POST方法的请求
	 * <br>默认请求头：
	 * <br> accept = *\/*
	 * <br> connection = Keep-Alive
	 * <br> Cache-Control = no-cache
	 * <br> user-agent = Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)
	 * <br> Accept-Charset = UTF-8
	 * <br> Content-Type = application/json;charset=UTF-8
	 * @param url 发送请求的 URL
	 * @param param 请求参数，请求参数应该是 name1=value1&name2=value2 的形式。
	 * @param body 请求体参数text/json
	 * @param header 请求头Map<String, String>
	 * @return 所代表远程资源的响应结果
	 */
	public static String sendSSLPost(String url, String param, String body, Map<String, String> header) {
		String result = "";
		InputStream in = null;
		OutputStream out = null;
		HttpsURLConnection connection = null;
		try {
			url = url + (StringUtils.isNotEmpty(param) ? "?" + param : "");
			SSLContext sc = SSLContext.getInstance("SSL");
			sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());
			URL realUrl = new URL(url);
			connection  = (HttpsURLConnection) realUrl.openConnection();
			connection.setRequestMethod(HttpMethod.POST.name());
			connection.setDoOutput(true);
			connection.setDoInput(true);
			
			Map<String, String> defaultHeader = createRequestHeader(header);
			for(Map.Entry<String, String> entry : defaultHeader.entrySet()) {
				connection.setRequestProperty(entry.getKey(), entry.getValue());
			}
			
			String charset = defaultHeader.get("Accept-Charset");
			charset = StringUtils.isNotEmpty(charset) ? charset : StandardCharsets.UTF_8.toString();
			
			if(StringUtils.isNotEmpty(body)) {
				out = connection.getOutputStream();
				IOUtils.write(body, out, charset);
			}
			
			connection.setSSLSocketFactory(sc.getSocketFactory());
			connection.setHostnameVerifier(new TrustAnyHostnameVerifier());
			connection.connect();
			
			in = connection.getInputStream();
			result = IOUtils.toString(in, charset);
			log.info("调用HttpUtils.sendSSLPost Response, url={}, param={}, result={}", url, param, result.length() > 1024 ? result.substring(0, 1024) : result);
		} catch (ConnectException e) {
			log.error("调用HttpUtils.sendSSLPost ConnectException, url={}, param={}, body={}", url, param, body, e);
		} catch (SocketTimeoutException e) {
			log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url={}, param={}, body={}", url, param, body, e);
		} catch (IOException e) {
			log.error("调用HttpUtils.sendSSLPost IOException, url={}, param={}, body={}", url, param, body, e);
		} catch (Exception e) {
			log.error("调用HttpsUtil.sendSSLPost Exception, url={}, param={}, body={}", url, param, body, e);
		}finally {
			if(in != null) {
				try {
					in.close();
				} catch (IOException e) {
					log.error(e.getMessage(), e);
				}
			}
			if(out != null) {
				try {
					out.close();
				} catch (IOException e) {
					log.error(e.getMessage(), e);
				}
			}
			if(in != null) {
				try {
					in.close();
				} catch (IOException e) {
					log.error(e.getMessage(), e);
				}
			}
			if(connection != null) {
				connection.disconnect();
			}
		}
		return result.toString();
	}
	
	/**
	 * <br>默认请求头：
	 * <br> accept = *\/*
	 * <br> connection = Keep-Alive
	 * <br> Cache-Control = no-cache
	 * <br> user-agent = Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)
	 * <br> Accept-Charset = UTF-8
	 * <br> Content-Type = application/json;charset=UTF-8
	 * @param header 请求头
	 * @return 请求头
	 */
	public static Map<String, String> createRequestHeader(Map<String, String> header){
		Map<String, String> defaultHeader = new HashMap<String, String>();
		defaultHeader.put("accept", "*/*");
		defaultHeader.put("connection", "Keep-Alive");
		defaultHeader.put("Cache-Control", "no-cache");
		defaultHeader.put("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
		defaultHeader.put("Accept-Charset", StandardCharsets.UTF_8.name());
		defaultHeader.put("Content-Type", MediaType.APPLICATION_JSON_UTF8_VALUE);
		if(header != null && !header.isEmpty()) {
			header.forEach((key, value) -> {
				defaultHeader.put(key, value);
			});
		}
		return defaultHeader;
	}

	private static class TrustAnyTrustManager implements X509TrustManager {
		@Override
		public void checkClientTrusted(X509Certificate[] chain, String authType) {
		}

		@Override
		public void checkServerTrusted(X509Certificate[] chain, String authType) {
		}

		@Override
		public X509Certificate[] getAcceptedIssuers() {
			return new X509Certificate[] {};
		}
	}

	private static class TrustAnyHostnameVerifier implements HostnameVerifier {
		@Override
		public boolean verify(String hostname, SSLSession session) {
			return true;
		}
	}
	
}